﻿/*************************************************************************************************************
  Copyright (c) 2010, Zack Evans
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of CapriSoft nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
**************************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Data;
using System.Globalization;
using System.Data.Common;
using System.Reflection;
using System.ComponentModel;
using System.Diagnostics;
using System.Xml;
using System.Reflection.Emit;
using System.Threading;
using System.Runtime.CompilerServices;

namespace Extention
{   
    public class ListEx<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable, IDataReader,IDisposable
    {
        #region Fields
        private bool isDisposed = false;

        private IDataReader internalReader = null;
        private Type typeToMap = null;
        private Dictionary<string, Type> fieldInformation = null;        
        private string _primaryKey = string.Empty;
        private T _currentObject { get; set; }
        private int _currentRecord;
        private bool _eof;
        private List<T> innerList = null;
        private SortedDictionary<int, object> _sortedKeyCache { get; set; }
        #endregion Fields
      
        #region Collection Implementation

        public ListEx()
        {
            _sortedKeyCache = new SortedDictionary<int, object>();
            innerList = new List<T>();
            _currentRecord = -1;
            fieldInformation = new Dictionary<string, Type>();
        }
        public ListEx(IEnumerable<T> collection)
        {
            _sortedKeyCache = new SortedDictionary<int, object>();
            innerList = new List<T>(collection);
            _currentRecord = -1;
            fieldInformation = new Dictionary<string, Type>();
        }
        public ListEx(int capacity)
        {
            _sortedKeyCache = new SortedDictionary<int, object>();
            innerList = new List<T>(capacity);
            _currentRecord = -1;
            fieldInformation = new Dictionary<string, Type>();
        }
        public ListEx(IDataReader reader) : this(reader, default(Type))
        {          
        }       
        public ListEx(IDataReader reader, Type typeToMap)
        {
            if (reader == null)
                throw new ArgumentNullException("reader argument cannot be null");

            Type genType = this.GetType().GetGenericArguments()[0];

            if (typeToMap != null && !genType.Equals(typeToMap))
                throw new ApplicationException("type to map must be of same underlying generic list type");

            _sortedKeyCache = new SortedDictionary<int, object>();
            innerList = new List<T>();
            _currentRecord = -1;

            if (typeToMap != null)
                fieldInformation = ReflectHelper.ReadTypeOnly(typeToMap);
            else fieldInformation = new Dictionary<string, Type>();

            if(typeToMap != default(Type))
                ConstructObject(reader, typeToMap);
            else ConstructObject(reader);
        }                     

        public int IndexOf(T item)
        {
            if (this.innerList != null)
                return innerList.IndexOf(item);
            else return 0;
        }
        public void Insert(int index, T item)
        {
            if (this.innerList != null)
                innerList.Insert(index, item);
            else throw new ApplicationException();
        }
        public void RemoveAt(int index)
        {
            if (this.innerList != null)
                innerList.RemoveAt(index);
            else throw new ApplicationException();
        }
       
        public void Add(T item)
        {
            if (this.innerList != null)
                innerList.Add(item);
            else throw new ApplicationException();

            if (fieldInformation.Count == 0 && innerList.Count > 0)
                fieldInformation = GetFieldInformation(innerList[0].GetType());

            if (!String.IsNullOrEmpty(_primaryKey) && FindByValue(_primaryKey, ReflectHelper.ReadData(item, _primaryKey)).Count() == 0)
                _sortedKeyCache.Add(innerList.Count - 1, ReflectHelper.ReadData(item, _primaryKey));
            else if (!String.IsNullOrEmpty(_primaryKey) && FindByValue(_primaryKey, ReflectHelper.ReadData(item, _primaryKey)).Count() > 0)
                throw new ApplicationException("Primary Key violation!!!");
        }
        public void Clear()
        {
            _sortedKeyCache.Clear();
            _sortedKeyCache = new SortedDictionary<int, object>();

            if (this.innerList != null)
                innerList.Clear();
            else throw new ApplicationException();

            innerList = new List<T>();
        }
        public bool Contains(T item)
        {
            if (this.innerList != null)
                return innerList.Contains(item);
            else throw new ApplicationException();
        }
        public void CopyTo(T[] array, int arrayIndex)
        {
            if (this.innerList != null)
                innerList.CopyTo(array, arrayIndex);
            else throw new ApplicationException();
        }
        public int Count
        {
            get
            {
                if (isDisposed)
                    return default(int);

                if (this.innerList != null) 
                    return innerList.Count; 
                else throw new ApplicationException(); 
            }
        }
        public bool IsReadOnly
        {
            get { return false; }
        }
        public bool Remove(T item)
        {
            if (this.innerList == null)
                throw new ApplicationException();

            if (!String.IsNullOrEmpty(_primaryKey))
            {
                int itemIndex = this.innerList.IndexOf(item);
                if (_sortedKeyCache.ContainsKey(itemIndex))
                    _sortedKeyCache.Remove(itemIndex);
            }

            return innerList.Remove(item);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public IEnumerator<T> GetEnumerator()
        {
            if (this.innerList == null)
                throw new ApplicationException();

            foreach (T t in this.innerList)
                yield return t;
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            IEnumerable<T> enumerator = this as IEnumerable<T>;
            return enumerator.GetEnumerator();
        }

        int IList.Add(object value)
        {
            this.Add((T)value);
            return 1;
        }
        bool IList.Contains(object value)
        {
            return this.Contains((T)value);
        }
        int IList.IndexOf(object value)
        {
            return this.IndexOf((T)value);
        }
        void IList.Insert(int index, object value)
        {
            this.Insert(index, (T)value);
        }
        bool IList.IsFixedSize
        {
            get { return false; }
        }
        void IList.Remove(object value)
        {
            if (this.innerList == null)
                throw new ApplicationException();

            innerList.Remove((T)value);
        }
        void ICollection.CopyTo(Array array, int index)
        {
            this.CopyTo((T[])array, index);
        }
        bool ICollection.IsSynchronized
        {
            get { return false; }
        }
        static readonly object lockSync = new object();
        object ICollection.SyncRoot
        {
            get { return lockSync; }
        }

        #endregion Collection Implementation

        #region IDataReader

        public void Close()
        {
            if (this.innerList == null)
                throw new ApplicationException();

            if (this.innerList != null && this.innerList.Count > 0)
                _currentObject = this.innerList[0];
            else _currentObject = default(T);

            _currentRecord = -1;
            _eof = false;

            if (this.internalReader != null && !this.internalReader.IsClosed)
                this.internalReader.Close();

        }  
        public int Depth
        {
            get
            {
                return 0;
            }
        }
        public DataTable GetSchemaTable()
        {
            DataTable schema = new DataTable("SchemaTable");
            schema.Locale = CultureInfo.InvariantCulture;
            schema.MinimumCapacity = fieldInformation.Count;

            schema.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(string)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.BaseTableName, typeof(string)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.ColumnName, typeof(string)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.DataType, typeof(object)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.IsAliased, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.IsExpression, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.IsKey, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.IsLong, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.IsUnique, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(short)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.NumericScale, typeof(short)).ReadOnly = true;
            schema.Columns.Add(SchemaTableColumn.ProviderType, typeof(int)).ReadOnly = true;

            schema.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string)).ReadOnly = true;
            schema.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string)).ReadOnly = true;
            schema.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool)).ReadOnly = true;
            schema.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool)).ReadOnly = true;

            string[] columnNames;

            columnNames = fieldInformation.Keys.ToArray();

            // null marks columns that will change for each row
            object[] schemaRow = new object[] { 
					true,					// 00- AllowDBNull
					null,					// 01- BaseColumnName
					string.Empty,			// 02- BaseSchemaName
					string.Empty,			// 03- BaseTableName
					null,					// 04- ColumnName
					null,					// 05- ColumnOrdinal
					int.MaxValue,			// 06- ColumnSize
					typeof(string),			// 07- DataType
					false,					// 08- IsAliased
					false,					// 09- IsExpression
					false,					// 10- IsKey
					false,					// 11- IsLong
					false,					// 12- IsUnique
					DBNull.Value,			// 13- NumericPrecision
					DBNull.Value,			// 14- NumericScale
					(int) DbType.String,	// 15- ProviderType
					string.Empty,			// 16- BaseCatalogName
					string.Empty,			// 17- BaseServerName
					false,					// 18- IsAutoIncrement
					false,					// 19- IsHidden
					true,					// 20- IsReadOnly
					false					// 21- IsRowVersion
			  };

            for (int i = 0; i < columnNames.Length; i++)
            {
                schemaRow[1] = columnNames[i]; // Base column name
                schemaRow[4] = columnNames[i]; // Column name
                schemaRow[5] = i; // Column ordinal

                schema.Rows.Add(schemaRow);
            }

            return schema;
        }
        public bool IsClosed
        {
            get { return _eof; }
        }
        public bool NextResult()
        {
            return false;
        }
        public bool Read()
        {
            if (isDisposed)
                return false;

            return ReadNextRecord();
        }
        public int RecordsAffected
        {
            get { return -1; }
        }
        public void Dispose()
        {
            if (this.innerList == null)
                throw new ApplicationException();

            innerList.Clear();

            _currentObject = default(T);
            _primaryKey = null;
            fieldInformation = null;
            innerList = null;
            _currentRecord = -1;
            _eof = false;
            fieldInformation = new Dictionary<string, Type>();
            isDisposed = true;
        }
        public int FieldCount
        {
            get { return fieldInformation.Count; }
        }
        public bool GetBoolean(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            bool outResult = default(bool);


            bool.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public byte GetByte(int i)
        {
            return Byte.Parse(GetString(i), CultureInfo.CurrentCulture);
        }
        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            return CopyFieldToArray(i, fieldOffset, buffer, bufferoffset, length);
        }
        public char GetChar(int i)
        {
            return Char.Parse(GetString(i));
        }
        public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            return CopyFieldToArray(i, fieldoffset, buffer, bufferoffset, length);
        }        
        private long CopyFieldToArray(int field, long fieldOffset, Array destinationArray, int destinationOffset, int length)
        {
            if (field < 0 || field >= fieldInformation.Count)
                throw new ApplicationException("field");

            if (fieldOffset < 0 || fieldOffset >= int.MaxValue)
                throw new ArgumentOutOfRangeException("fieldOffset");

            // Array.Copy(...) will do the remaining argument checks

            if (length == 0)
                return 0;

            string value = GetString(field);

            if (value == null)
                value = string.Empty;

            Debug.Assert(fieldOffset < int.MaxValue);

            Debug.Assert(destinationArray.GetType() == typeof(char[]) || destinationArray.GetType() == typeof(byte[]));

            if (destinationArray.GetType() == typeof(char[]))
                Array.Copy(value.ToCharArray((int)fieldOffset, length), 0, destinationArray, destinationOffset, length);
            else
            {
                char[] chars = value.ToCharArray((int)fieldOffset, length);
                byte[] source = new byte[chars.Length]; ;

                for (int i = 0; i < chars.Length; i++)
                    source[i] = Convert.ToByte(chars[i]);

                Array.Copy(source, 0, destinationArray, destinationOffset, length);
            }

            return length;
        }
        public IDataReader GetData(int i)
        {
            return this;
        }
        public string GetDataTypeName(int i)
        {
            if (fieldInformation == null)
                fieldInformation = GetFieldInformation();
            else
            {
                int x = 0;
                foreach (string key in fieldInformation.Keys)
                {
                    if (x == i)
                        return fieldInformation[key].Name;
                    x++;
                }
            }
            throw new ApplicationException("could not determine data type name");
        }
        public DateTime GetDateTime(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            DateTime outResult = default(DateTime);

            DateTime.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public decimal GetDecimal(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            decimal outResult = default(decimal);

            decimal.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public double GetDouble(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            double outResult = default(double);

            double.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public Type GetFieldType(int i)
        {
            return this[fieldInformation.Keys.ToArray()[i]].GetType();
        }
        public float GetFloat(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            float outResult = default(float);

            float.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public Guid GetGuid(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            Guid outResult = default(Guid);

            Guid.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public short GetInt16(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            Int16 outResult = default(Int16);

            Int16.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public int GetInt32(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            Int32 outResult = default(Int32);

            Int32.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public long GetInt64(int i)
        {
            //here the ordinal is passed on on the current record to return the value.
            long outResult = default(long);

            Int64.TryParse(this[fieldInformation.Keys.ToArray()[i]].ToString(), out outResult);

            return outResult;
        }
        public string GetName(int i)
        {
            return fieldInformation.Keys.ToArray()[i];
        }
        public int GetOrdinal(string name)
        {
            return PropertyIndex(name);
        }
        public string GetString(int i)
        {
            return this[fieldInformation.Keys.ToArray()[i]].ToString();
        }
        public object GetValue(int i)
        {
            return this[fieldInformation.Keys.ToArray()[i]];
        }
        public int GetValues(object[] values)
        {
            throw new NotImplementedException();
        }
        public bool IsDBNull(int i)
        {
            return (this[fieldInformation.Keys.ToArray()[i]] == null) ? true : false;
        }

        #endregion IDataReader

        #region Collection Indexer

        public T this[int index]
        {
            get
            {
                if (isDisposed)
                    return default(T);

                if (this.innerList == null)
                    throw new ApplicationException();

                if (this.innerList.Count == 0)
                    throw new ApplicationException("invalid index");

                return innerList[index];
            }
            set
            {
                if (this.innerList == null)
                    throw new ApplicationException();

                innerList[index] = value;
            }
        }
       
        object IList.this[int index]
        {
            get
            {
                if (isDisposed)
                    return default(object);

                return this[index];
            }
            set
            {
                this[index] = (T)value;
            }
        }       

        #endregion Collection Indexer

        #region IDataReader Indexers

        private int PropertyIndex(string name)
        {
            if (isDisposed)
                return default(int);

            for (int x = 0; x < fieldInformation.Keys.Count; x++)
            {
                if (fieldInformation.Keys.ToArray()[x] == name)
                    return x;
            }
            throw new ApplicationException("could not find property index");
        }

        /// <summary>
        /// IDataReader Implemetation
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object this[string name]
        {
            get
            {
                if (isDisposed)
                    return default(object);

                return ((IDataReader)this)[PropertyIndex(name)];
            }
        }
        /// <summary>
        /// Return all property values within the list
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object PropertyValues(string propertyName)
        {
            if (isDisposed)
                return default(object);

            List<object> dataContext = new List<object>();

            //ensure begin read
            this.Close();

            while (this.Read())
            {
                dataContext.Add(this.GetValue(PropertyIndex(propertyName)));
            }

            this.Close();

            return dataContext.ToArray();
        }
        /// <summary>
        /// IDataReader Implemetation
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        object IDataRecord.this[int fieldIndex]
        {
            get
            {
                if (isDisposed)
                    return default(object);

                return ReadField(fieldIndex);
            }
        }
 
        #endregion IDataReader Indexers               

        #region IDataReaderHelper

        public bool ReadNextRecord()
        {
            if (this.innerList == null)
                throw new ApplicationException();

            _currentRecord++;

            if (this.innerList != null && this.innerList.Count > _currentRecord)
                _currentObject = this.innerList[_currentRecord];
            else if (this.innerList != null && this.innerList.Count >= _currentRecord)
            {
                _eof = true;
                _currentObject = default(T);
                return false;
            }
            else
            {
                _currentObject = default(T);
                return false;
            }

            return true;
        }
        private object ReadField(int fieldIndex)
        {
            if (_currentObject != null)
                return ReflectHelper.ReadData(_currentObject, this.fieldInformation.Keys.ToArray()[fieldIndex]);
            else 
                throw new ApplicationException("no current object to read");
        }        

        
        #endregion IDataReaderHelper

        #region IDataReader Extention

        public DataSet CreateDataSet()
        {
            return CreateDataSet("dataSet1");
        }
        public DataSet CreateDataSet(string DataSetName)
        {  
            DataSet _dataSet =
                new DataSet(DataSetName);

            _dataSet.Tables.Add(CreateDataTable());

            return _dataSet;
        }
        public DataSet CreateDataSet(string DataSetName,string TableName)
        {
            DataSet _dataSet =
                new DataSet(DataSetName);

            _dataSet.Tables.Add(CreateDataTable(TableName));

            return _dataSet;
        }
        public DataTable CreateDataTable()
        {
            return CreateDataTable("table1");
        }
        public DataTable CreateDataTable(string DataTableName)
        {
            DataTable _table = CreateTableFromSchema(DataTableName);
            while (this.Read())
            {
                DataRow newRow = _table.NewRow();
                for (int i = 0; i < _table.Columns.Count; i++)
                    newRow[i] = GetValue(i);

                _table.Rows.Add(newRow);
            }
            this.Close();
            return _table;
        }

        private DataTable CreateTableFromSchema(string TableName)
        {
            if (this.innerList == null)
                throw new ApplicationException();

            DataTable _table = new DataTable(TableName);

            if (this.innerList != null && this.fieldInformation.Count > 0)
                foreach (string columnName in fieldInformation.Keys)
                    _table.Columns.Add(columnName, ReflectHelper.GetDataType(this.innerList[0],columnName));

            return _table;
        }

        #endregion IDataReader Extention

        #region Extended

        public T[] FindByValue(string PropertyName, object value)
        {
            if (this.innerList == null)
                throw new ApplicationException();

            List<T> result = new List<T>();
            if (PropertyName == _primaryKey)
            {
                if (this._sortedKeyCache.ContainsValue(value))
                {
                    List<KeyValuePair<int, object>> results =
                             (List<KeyValuePair<int, object>>)this._sortedKeyCache.Where(p => ((Int64)p.Value).Equals(value)).ToList();

                    foreach (KeyValuePair<int, object> keyPair in results)
                        result.Add(this.innerList[keyPair.Key]);
                    
                    return result.ToArray();
                }
                else result.ToArray();
            }

            foreach(T t in this.innerList)
            {
                foreach (string property in fieldInformation.Keys)
                {
                    if (PropertyName == property)
                    {
                        if (ReflectHelper.ReadData(t, property) == value)
                            result.Add(t);
                    }
                }               
            }
            return result.ToArray();
        }
        public ListEx<T> Where(string PropertyName, object value)
        {
            if (this.innerList == null)
                throw new ApplicationException();

            ListEx<T> result = new ListEx<T>();
            if (PropertyName == _primaryKey)
            {
                if (this._sortedKeyCache.ContainsValue(value))
                {
                    List<KeyValuePair<int, object>> results =
                             (List<KeyValuePair<int, object>>)this._sortedKeyCache.Where(p => ((Int64)p.Value).Equals(value)).ToList();

                    foreach (KeyValuePair<int, object> keyPair in results)
                        result.Add(this.innerList[keyPair.Key]);

                    return result;
                }
                else result.ToArray();
            }

            foreach (T t in this.innerList)
            {
                foreach (string property in fieldInformation.Keys)
                {
                    if (PropertyName == property)
                    {
                        if (ReflectHelper.ReadData(t, property).Equals(value))
                            result.Add(t);
                    }
                }
            }
            return result;
        }
       
        public ListEx<T> Where(Predicate<T> predicate)
        {
            ListEx<T> result = new ListEx<T>();

            foreach (T t in innerList)
                if (predicate(t))
                    result.Add(t);
            return result;
        }
        public IEnumerable<T> WhereEnumerator(Predicate<T> predicate)
        {           
            IEnumerator<T> enumerator = (IEnumerator<T>)this.GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (predicate(enumerator.Current))
                    yield return enumerator.Current;
            }
        }       

        #endregion Extended

        #region Dynamic Assembly

        public static explicit operator ListEx<GenericDataObject>(ListEx<T> wrapper)
        {
            return new ListEx<GenericDataObject>(wrapper.internalReader);
        }
        public Dictionary<string, Type> GetFieldInformation()
        {
            return fieldInformation;
        }
        private Dictionary<string, Type> GetFieldInformation(IDataReader reader)
        {
            fieldInformation = new Dictionary<string, Type>();           
            int propertyCount = -1;

            if (reader.GetType().Equals(typeof(ListEx<T>)))
            {
                object genericType = null;

                if (this.typeToMap != default(Type))
                {
                    genericType = this.typeToMap;
                    propertyCount = ((Type)genericType).GetCustomAttributes(typeof(ReadableProperty), true).Length;
                    if (propertyCount == 0)
                        propertyCount = -1;
                }   
            }

            if (fieldInformation.Count == 0 && typeToMap == null)
            {
                for (int x = 0; x < ((propertyCount > -1) ? propertyCount : (!reader.IsClosed) ? reader.FieldCount : 0); x++)
                    fieldInformation.Add(reader.GetName(x), ReflectHelper.GetDataType(reader.GetDataTypeName(x)));

                if (fieldInformation != null && fieldInformation.Count == 0)
                    throw new ApplicationException("the reader has no properties marked with the readable attribute");
            }
            else fieldInformation = ReflectHelper.ReadTypeOnly(typeToMap);
            return fieldInformation;
        }
        private Dictionary<string, Type> GetFieldInformation(Type type)
        {
            return ReflectHelper.ReadTypeOnly(type);
        }       
        private void ConstructObject(IDataReader reader)
        {                      
            this.internalReader = reader;
            this.fieldInformation = GetFieldInformation(this.internalReader);
        }
        private void ConstructObject(IDataReader reader, Type typeToMap)
        {
            this.internalReader = reader; 
            this.typeToMap = typeToMap;
            this.fieldInformation = GetFieldInformation(this.internalReader);
        }

        public static Type BuildDynamicTypeWithProperties(Dictionary<string, Type> fieldInformation)
        {
            if (fieldInformation == null)
                throw new ApplicationException("no field information availabe to construct type");

            if (fieldInformation.Count == 0)
                throw new ApplicationException("no field information availabe to construct type");

            AppDomain myDomain = Thread.GetDomain();
            AssemblyName myAsmName = new AssemblyName();
            myAsmName.Name = "MyDynamicAssembly";

            // To generate a persistable assembly, specify AssemblyBuilderAccess.RunAndSave.
            AssemblyBuilder myAsmBuilder = myDomain.DefineDynamicAssembly(myAsmName,
                                                            AssemblyBuilderAccess.RunAndSave);
            // Generate a persistable single-module assembly.
            ModuleBuilder myModBuilder =
                myAsmBuilder.DefineDynamicModule(myAsmName.Name, myAsmName.Name + ".dll");

            TypeBuilder myTypeBuilder = myModBuilder.DefineType("DataObjectChild",
                                                                TypeAttributes.Public,typeof(GenericDataObject));

            foreach (string propertyName in fieldInformation.Keys)
            {

                FieldBuilder customerNameBldr = myTypeBuilder.DefineField(propertyName,
                                                                fieldInformation[propertyName],
                                                                FieldAttributes.Private);

                // The last argument of DefineProperty is null, because the
                // property has no parameters. (If you don't specify null, you must
                // specify an array of Type objects. For a parameterless property,
                // use an array with no elements: new Type[] {})
                PropertyBuilder custNamePropBldr = myTypeBuilder.DefineProperty(propertyName,
                                                                 System.Reflection.PropertyAttributes.HasDefault,
                                                                 fieldInformation[propertyName],
                                                                 null);

                Type[] ctorParams = new Type[] { typeof(string) };
                ConstructorInfo classCtorInfo = typeof(ReadableProperty).GetConstructor(ctorParams);

                CustomAttributeBuilder myCABuilder = new CustomAttributeBuilder(
                                    classCtorInfo,
                                    new object[] { propertyName });

                myTypeBuilder.SetCustomAttribute(myCABuilder);

                // The property set and property get methods require a special
                // set of attributes.
                MethodAttributes getSetAttr =
                    MethodAttributes.Public | MethodAttributes.SpecialName |
                        MethodAttributes.HideBySig;

                // Define the "get" accessor method for CustomerName.
                MethodBuilder custNameGetPropMthdBldr =
                    myTypeBuilder.DefineMethod("get_" + propertyName,
                                               getSetAttr,
                                               fieldInformation[propertyName],
                                               Type.EmptyTypes);

                ILGenerator custNameGetIL = custNameGetPropMthdBldr.GetILGenerator();

                custNameGetIL.Emit(OpCodes.Ldarg_0);
                custNameGetIL.Emit(OpCodes.Ldfld, customerNameBldr);
                custNameGetIL.Emit(OpCodes.Ret);

                // Define the "set" accessor method for CustomerName.
                MethodBuilder custNameSetPropMthdBldr =
                    myTypeBuilder.DefineMethod("set_" + propertyName,
                                               getSetAttr,
                                               null,
                                               new Type[] { fieldInformation[propertyName] });

                ILGenerator custNameSetIL = custNameSetPropMthdBldr.GetILGenerator();

                custNameSetIL.Emit(OpCodes.Ldarg_0);
                custNameSetIL.Emit(OpCodes.Ldarg_1);
                custNameSetIL.Emit(OpCodes.Stfld, customerNameBldr);
                custNameSetIL.Emit(OpCodes.Ret);

                // Last, we must map the two methods created above to our PropertyBuilder to 
                // their corresponding behaviors, "get" and "set" respectively. 
                custNamePropBldr.SetGetMethod(custNameGetPropMthdBldr);
                custNamePropBldr.SetSetMethod(custNameSetPropMthdBldr);
            }


            Type retval = myTypeBuilder.CreateType();

            // Save the assembly so it can be examined with Ildasm.exe,
            // or referenced by a test program.
            myAsmBuilder.Save(myAsmName.Name + ".dll");
            return retval;
        }

      
        public void Fill()
        {
            if (this.internalReader == null)
                throw new ApplicationException("no data to read");

            if (this.internalReader != null)
                if (this.internalReader.GetType().Equals(typeof(ListEx<GenericDataObject>)))
                    ((ListEx<GenericDataObject>)this.internalReader).Fill();
        
            if (this.internalReader.IsClosed)
                throw new ApplicationException("the reader is closed");

            while (this.internalReader.Read())
            {
                object genericType = null;
                Type generatedType = null;

                if (this.typeToMap == default(Type))
                {
                    generatedType =
                        BuildDynamicTypeWithProperties(fieldInformation);
                }
                else { genericType = Activator.CreateInstance(typeToMap); }

                if (generatedType == null)
                    generatedType = genericType.GetType();

                object[] customAttributes =
                  generatedType.GetCustomAttributes(typeof(ReadableProperty), true);

                genericType = Activator.CreateInstance(generatedType);
                for (int x = 0; x < this.internalReader.FieldCount; x++)
                {
                    object value = this.internalReader.GetValue(x);
                    string readerPropertyName = this.internalReader.GetName(x);

                    PropertyInfo[] properties = generatedType.GetProperties();

                    foreach (PropertyInfo propInfo in properties)
                    {
                        if (propInfo.Name == readerPropertyName)
                        {
                            propInfo.SetValue(genericType, value, null);
                            break;
                        }
                    }
                }
                this.innerList.Add((T)genericType);
            }

            this.internalReader.Close();
        }

        #endregion Dynamic Assembly       
    }
    public class GenericDataObject
    {
    }
}
